import { getQueryParams } from '@/utils/utils';
import { message } from 'antd';
import type { AxiosInstance, AxiosResponse } from 'axios';
import axios from 'axios';
import { cloneDeep } from 'lodash-es';
import qs from 'qs';
import { history } from 'umi';
import { clearStorage } from '../storage';
import { defaultConfig } from './config';
import { ContentType } from './enum';
import type { CreateAxiosConfig } from './interface.d';
import { clearLoading, showLoading } from './renderLoading';

// 接口公共参数字段
const commonParamsMap: any = [];

const errorResult: string = '__ERROR_RESULT__';
const errorResult401: string = '__ERROR_RESULT__401__';
const errorResult500: string = '__ERROR_RESULT__500__';

function transformFormData<T extends RecordItem>(param: T) {
  const fd = new FormData();
  Object.keys(param).forEach((key) => {
    fd.append(key, param[key]);
  });
  return fd;
}

export class Axios {
  private instance: AxiosInstance;

  constructor(options: CreateAxiosConfig) {
    this.instance = axios.create(options);
    this.setHeaders();
  }

  /**
   * 指定post请求方式的content-type
   */
  private setHeaders() {
    this.instance.defaults.headers.post['Content-Type'] = ContentType.FORM_URLENCODED;
  }

  static transformResponse(res: ResponseResult, conf: CreateAxiosConfig): any {
    const { data, msg, code } = res;
    if (code === 401) {
      Promise.reject(defaultConfig[401]);
      message.error(defaultConfig[401]);
      return errorResult401;
    }
    if (code !== 200) {
      !conf.hiddenErrorMessage && message.error(msg || defaultConfig.other);
      return errorResult;
    }

    // 平台接口返回格式没有包裹一层data
    return conf.isPlatform ? res : data;
  }

  static transformRequest(config: CreateAxiosConfig): CreateAxiosConfig {
    const conf: CreateAxiosConfig = cloneDeep(config);

    const commonParams: RecordItem = {};
    const queryParams = getQueryParams();

    commonParamsMap.forEach((key: any) => {
      if (key === 'memberId') {
        commonParams.memberId = queryParams.userId;
      } else {
        commonParams[key] = queryParams[key];
      }
    });

    conf.params = { ...commonParams, ...(conf.params || {}) };
    conf.headers = {
      ...conf.headers,
    };

    if (conf.method?.toLocaleUpperCase() === 'POST') {
      if (conf.isFormData) {
        conf.data = transformFormData(conf.params);
      } else {
        conf.data = qs.stringify(conf.params);
      }

      conf.params = null;
    }

    !conf.disableLoading && showLoading(conf.url as string);
    return conf;
  }

  public request<T = any>(config: CreateAxiosConfig): Promise<T> {
    const conf = Axios.transformRequest(config);

    return new Promise((resolve, reject) => {
      this.instance
        .request(conf)
        .then((res: AxiosResponse<ResponseResult>) => {
          !conf.disableLoading && clearLoading(conf.url as string);

          const { data, headers } = res;
          const ret: any = Axios.transformResponse(data, conf);
          // 登录失效或无权限
          if (ret === errorResult401) {
            clearStorage();
            history.push('/user/login');
            return;
          }
          // 接口正常返回数据
          if (ret !== errorResult && ret !== errorResult500) {
            const retData: any = ret;
            // 如果需要返回http headers
            if (conf.getHeaders) {
              retData.headers = headers;
            }
            resolve(retData as unknown as Promise<T>);
          } else {
            /**
             * 其他各种类型错误，需要在前端处理错误的，将返回暴露出去
             * 调用处接口需要使用try catch，抛出的错误将抛到catch error对象中
             */
            reject(
              conf.handError ? (data as unknown as Promise<T>) : new Error(JSON.stringify(data)),
            );
          }
        })
        .catch((e: Error) => {
          !conf.disableLoading && clearLoading(conf.url as string);
          message.error(`系统开小差了(${e.message})，请刷新后重试或联系管理员`);
          reject(e);
        });
    });
  }

  public upload<T = any>(url: string, params: UploadFileParams): Promise<T> {
    const formData = new window.FormData();

    if (params.data) {
      Object.keys(params.data).forEach((key) => {
        formData.append(key, params.data?.[key]);
      });
    }

    formData.append(params.name || 'file', params.file, params.filename);

    return this.instance.post(url, formData, {
      headers: {
        'Content-Type': ContentType.FORM_DATA,
      },
    });
  }
}

export const $axios = new Axios({
  baseURL: process.env.API_PREFIX,
  responseType: 'json',
});
